﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Collections;
using System.ComponentModel;
using System.Linq.Expressions;
using System.Reflection;

namespace DBA.Common
{
    public static class LinqHelper
    {

        /// <summary>
        /// public static void Main()
        ///    {
        ///         inventoryBLL inv = new inventoryBLL();
        ///           DataSet1.sDataTable dtsku = inv.SelectEverything();
        ///          foreach (var items in dtsku.Select(r => r.item).SplitIntoGroups(10))
        ///           {
        ///            webservicefunction(items);
        ///           }
        ///     }
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="items"></param>
        /// <param name="N"></param>
        /// <returns></returns>
        public static IEnumerable<T[]> SplitIntoGroups<T>(this IEnumerable<T> items, int N)
        {
            if (items == null || N < 1)
                yield break;

            T[] group = new T[N];
            int size = 0;
            var iter = items.GetEnumerator();

            while (iter.MoveNext())
            {
                group[size++] = iter.Current;
                if (size == N)
                {
                    yield return group;
                    size = 0;
                    group = new T[N];
                }
            }
            if (size > 0)
                yield return group.Take(size).ToArray();
        }

        /// <summary>
        /// inv.SelectEverything().AtATime<Row>(10, webservicefunction);
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="list"></param>
        /// <param name="eachTime"></param>
        /// <param name="action"></param>
        public static void AtATime<T>(this IEnumerable<T> list, int eachTime, Action<IEnumerable<T>> action)
        {
            var taken = 0;
            while (list.Count() >= taken)
            {
                action(list.Skip(taken).Take(eachTime));
                taken += eachTime;
            }
        }
        /// <summary>
        ///     Orders the sequence by specific column and direction.
        ///     This method support multiple columns sorting like the following data
        ///     SortColumn = "aaa,"
        /// </summary>
        /// <param name="query">The query.</param>
        /// <param name="sortColumn">Sort column</param>
        /// <param name="direction">The sort column.</param>
        public static IQueryable<T> OrderWith<T>(this IQueryable<T> query, string sortColumn, string direction)
        {
            var columns = sortColumn.Split(' ', ',');
            var directions = direction.Split(' ', ',');

            for (var i = 0; i < columns.Length; i++)
            {
                var dir = directions.Length > i ? directions[i] : directions[directions.Length - 1];
                var methodName = string.Format("OrderBy{0}", dir.ToLower() == "asc" ? "" : "descending");
                var parameter = Expression.Parameter(query.ElementType, "p");
                var memberAccess = columns[i].Split('.').Aggregate<string, MemberExpression>(null,
                                                                                             (current, property) =>
                                                                                             Expression.Property(
                                                                                                 current ??
                                                                                                 (parameter as
                                                                                                  Expression), property));
                var orderByLambda = Expression.Lambda(memberAccess, parameter);

                if (i > 0)
                {
                    methodName = string.Format("ThenBy{0}", dir.ToLower() == "asc" ? "" : "descending");
                }

                var result = Expression.Call(
                    typeof(Queryable),
                    methodName,
                    new[] { query.ElementType, memberAccess.Type },
                    query.Expression,
                    Expression.Quote(orderByLambda));

                query = query.Provider.CreateQuery<T>(result);
            }

            return query;
        }



        //public static IQueryable<T> Where<T>(this IQueryable<T> query, Rule[] rules, bool isMatchAllRule)
        //{
        //    Expression currentExp = null;
        //    var parameter = Expression.Parameter(query.ElementType, "x");

        //    foreach (var r in rules)
        //    {
        //        var column = r.field;
        //        var value = r.data;
        //        var operation = (WhereOperation)StringEnum.Parse(typeof(WhereOperation), r.op);

        //        if (string.IsNullOrEmpty(column)) continue;

        //        var memberAccess = column.Split('.').Aggregate<string, MemberExpression>(null, (current, property) => Expression.Property(current ?? (parameter as Expression), property));

        //        //change param value type
        //        //necessary to getting bool from string
        //        var filter = Expression.Constant
        //        (
        //            Convert.ChangeType(value, memberAccess.Type)
        //        );

        //        Expression condition;
        //        switch (operation)
        //        {
        //            case WhereOperation.Contains: condition = Expression.Call(memberAccess, WhereOperationMethods.Contains, filter); break;
        //            case WhereOperation.NotContains: condition = Expression.Not(Expression.Call(memberAccess, WhereOperationMethods.Contains, filter)); break;
        //            case WhereOperation.BeginsWith: condition = Expression.Call(memberAccess, WhereOperationMethods.BeginsWith, filter); break;
        //            case WhereOperation.NotBeginsWith: condition = Expression.Not(Expression.Call(memberAccess, WhereOperationMethods.BeginsWith, filter)); break;
        //            case WhereOperation.EndsWith: condition = Expression.Call(memberAccess, WhereOperationMethods.EndsWith, filter); break;
        //            case WhereOperation.NotEndsWith: condition = Expression.Not(Expression.Call(memberAccess, WhereOperationMethods.EndsWith, filter)); break;
        //            case WhereOperation.LessThan: condition = Expression.LessThan(memberAccess, filter); break;
        //            case WhereOperation.LessThanOrEqual: condition = Expression.LessThanOrEqual(memberAccess, filter); break;
        //            case WhereOperation.GreaterThan: condition = Expression.GreaterThan(memberAccess, filter); break;
        //            case WhereOperation.GreaterThanOrEqual: condition = Expression.GreaterThanOrEqual(memberAccess, filter); break;
        //            case WhereOperation.Equal: condition = Expression.Equal(memberAccess, filter); break;
        //            default: condition = Expression.NotEqual(memberAccess, filter); break;
        //        }

        //        currentExp = currentExp == null ? condition : (isMatchAllRule ? Expression.AndAlso(currentExp, condition) : Expression.OrElse(currentExp, condition));
        //    }

        //    if (currentExp == null) return query;

        //    var result = Expression.Call(
        //           typeof(Queryable), "Where",
        //           new[] { query.ElementType },
        //           query.Expression,
        //           Expression.Lambda(currentExp, parameter));

        //    return query.Provider.CreateQuery<T>(result);
        //}

        public static void ForEach<T>(this IEnumerable<T> source, Action<T> action)
        {
            if (source == null) return;

            foreach (var item in source)
            {
                action(item);
            }
        }

        public static bool Any<T>(this IEnumerable<T> source, Func<T, bool> predicate, bool runAll)
        {
            var result = false;
            if (source == null) return false;

            foreach (var item in source)
            {
                result |= predicate(item);

                if (!runAll && result) break;
            }

            return result;
        }

        public static IOrderedQueryable<T> OrderBy<T>(this IQueryable<T> source, string property)
        {
            return ApplyOrder<T>(source, property, "OrderBy");
        }

        public static IOrderedQueryable<T> OrderByDescending<T>(this IQueryable<T> source, string property)
        {
            return ApplyOrder<T>(source, property, "OrderByDescending");
        }

        public static IOrderedQueryable<T> ThenBy<T>(this IOrderedQueryable<T> source, string property)
        {
            return ApplyOrder<T>(source, property, "ThenBy");
        }

        public static IOrderedQueryable<T> ThenByDescending<T>(this IOrderedQueryable<T> source, string property)
        {
            return ApplyOrder<T>(source, property, "ThenByDescending");
        }

        static IOrderedQueryable<T> ApplyOrder<T>(IQueryable<T> source, string property, string methodName)
        {
            string[] props = property.Split('.');
            Type type = typeof(T);
            ParameterExpression arg = Expression.Parameter(type, "x");
            Expression expr = arg;
            foreach (string prop in props)
            {
                // use reflection (not ComponentModel) to mirror LINQ
                PropertyInfo pi = type.GetProperty(prop);
                expr = Expression.Property(expr, pi);
                type = pi.PropertyType;
            }
            Type delegateType = typeof(Func<,>).MakeGenericType(typeof(T), type);
            LambdaExpression lambda = Expression.Lambda(delegateType, expr, arg);

            object result = typeof(Queryable).GetMethods().Single(
                    method => method.Name == methodName
                            && method.IsGenericMethodDefinition
                            && method.GetGenericArguments().Length == 2
                            && method.GetParameters().Length == 2)
                    .MakeGenericMethod(typeof(T), type)
                    .Invoke(null, new object[] { source, lambda });
            return (IOrderedQueryable<T>)result;
        }
    }
}
